昨天 Day 10,我把 File Monitor 正式接上 Detection Engine,讓 BehaviorGuard 可以即時監控檔案異動,並產生:
不過實際測試之後,我發現了一個新的問題。
同一個檔案明明只做了一次操作,File Monitor 有時卻會收到不只一個 MODIFIED Event。
例如:
MODIFIED
→ BG-FILE-001 HIGH
MODIFIED
→ BG-FILE-001 HIGH
如果未來 BehaviorGuard 監控的事件越來越多,這種情況可能會產生大量重複 Alert。
所以今天的目標不是繼續增加 Detection Rule,而是開始處理:
Alert Deduplication / Alert Suppression
也就是:
事件還是要持續監控、Detection Rule 還是要持續判斷,但是短時間內完全相同的 Alert,不需要一直通知。
今天我先理解了一個很重要的觀念:
Telemetry 數量不等於 Alert 數量。
例如系統可能收到:
MODIFIED
MODIFIED
MODIFIED
MODIFIED
MODIFIED
這五筆都是系統實際觀察到的 Telemetry,所以不能因為怕 Alert 太多,就乾脆停止監控。
比較合理的流程應該是:
File System
↓
watchdog
↓
File Telemetry
↓
Detection Engine
↓
Rule Match
↓
Alert Manager
↓
Deduplication / Suppression
↓
真正需要輸出的 Alert
也就是 Detection Engine 負責:
這個行為有沒有符合偵測規則?
Alert Manager 則負責:
雖然規則命中了,但是這個 Alert 有沒有必要再次通知?
這兩件事情其實是不同的。
今天新增:
detection/alert_manager.py
目前我設定:
SUPPRESSION_WINDOW = 5
也就是相同 Alert 在 5 秒內再次出現,就先 Suppress。
完整程式:
import time
# Store information about previously emitted alerts.
alert_history = {}
# Suppress identical alerts within this window.
SUPPRESSION_WINDOW = 5
def process_alert(alert, event):
"""
Process an alert and decide whether it should be emitted.
Alerts with the same:
rule_id + action + path
are treated as duplicates within the suppression window.
Returns:
{
"emit": True/False,
"suppressed_count": int
}
"""
rule_id = alert.get("rule_id", "")
action = event.get("action", "")
path = event.get("path", "")
alert_key = (
rule_id,
action,
path
)
now = time.time()
previous = alert_history.get(alert_key)
# First occurrence
if previous is None:
alert_history[alert_key] = {
"last_emit_time": now,
"suppressed_count": 0
}
return {
"emit": True,
"suppressed_count": 0
}
elapsed = now - previous["last_emit_time"]
# Duplicate inside suppression window
if elapsed < SUPPRESSION_WINDOW:
previous["suppressed_count"] += 1
return {
"emit": False,
"suppressed_count": previous["suppressed_count"]
}
# Suppression window expired
old_suppressed_count = previous["suppressed_count"]
alert_history[alert_key] = {
"last_emit_time": now,
"suppressed_count": 0
}
return {
"emit": True,
"suppressed_count": old_suppressed_count
}
目前 BehaviorGuard 使用三個資訊建立 Alert Key:
Rule ID
+
Action
+
Path
例如:
BG-FILE-001
MODIFIED
/tmp/behaviorguard_test/etc/passwd
會組成一個識別 Key。
如果 5 秒內再次出現完全相同的組合:
BG-FILE-001
MODIFIED
/tmp/behaviorguard_test/etc/passwd
BehaviorGuard 就知道:
這很可能是剛才那個事件造成的重複 Alert。
因此進行 Suppression。
但是如果出現:
BG-FILE-002
DELETED
/tmp/behaviorguard_test/etc/passwd
就不能 Suppress。
因為「修改 passwd」跟「刪除 passwd」明顯不是同一件事情。
接著修改:
agent/file_monitor.py
完整版本如下:
import time
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from detection.engine import analyze_file_event
from detection.alert_manager import process_alert
WATCH_PATH = "/tmp/behaviorguard_test"
class BehaviorGuardFileHandler(FileSystemEventHandler):
def print_event(self, action, path):
print("\n========== BehaviorGuard File Event ==========")
print(f"Action : {action}")
print(f"Path : {path}")
print("==============================================")
def print_alert(self, alert, event):
print("\n========== BehaviorGuard File Alert ==========")
print(f"Rule : {alert['rule_id']}")
print(f"Name : {alert['rule_name']}")
print(f"Severity : {alert['severity']}")
print(f"Action : {event['action']}")
print(f"Path : {event['path']}")
print(f"Description : {alert['description']}")
print("==============================================")
def process_event(self, action, path):
event = {
"action": action,
"path": path
}
# Raw telemetry
self.print_event(action, path)
# Detection Engine
alerts = analyze_file_event(event)
# Alert Manager
for alert in alerts:
result = process_alert(alert, event)
if result["emit"]:
self.print_alert(alert, event)
if result["suppressed_count"] > 0:
print(
f"[BehaviorGuard] "
f"{result['suppressed_count']} duplicate "
f"alert(s) were suppressed during "
f"the previous window."
)
else:
print(
f"[SUPPRESSED] "
f"{alert['rule_id']} duplicate alert "
f"for {path} "
f"(count: {result['suppressed_count']})"
)
def on_created(self, event):
if event.is_directory:
return
self.process_event(
"CREATED",
event.src_path
)
def on_modified(self, event):
if event.is_directory:
return
self.process_event(
"MODIFIED",
event.src_path
)
def on_deleted(self, event):
if event.is_directory:
return
self.process_event(
"DELETED",
event.src_path
)
def on_moved(self, event):
if event.is_directory:
return
self.process_event(
"MOVED",
event.dest_path
)
def monitor_files():
print("[BehaviorGuard] File Monitor started...")
print(f"[BehaviorGuard] Watching: {WATCH_PATH}")
event_handler = BehaviorGuardFileHandler()
observer = Observer()
observer.schedule(
event_handler,
WATCH_PATH,
recursive=True
)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[BehaviorGuard] File Monitor stopped.")
observer.stop()
observer.join()
if __name__ == "__main__":
monitor_files()
啟動:
PYTHONPATH=. python agent/file_monitor.py
接著建立測試用的 passwd:
mkdir -p /tmp/behaviorguard_test/etc
touch /tmp/behaviorguard_test/etc/passwd
然後短時間內連續修改:
echo "1" >> /tmp/behaviorguard_test/etc/passwd
echo "2" >> /tmp/behaviorguard_test/etc/passwd
echo "3" >> /tmp/behaviorguard_test/etc/passwd
echo "4" >> /tmp/behaviorguard_test/etc/passwd
這裡修改的是:
/tmp/behaviorguard_test/etc/passwd
並不是真正系統的:
/etc/passwd
所以可以安全地在 Lab 裡進行測試。
第一次修改時:
========== BehaviorGuard File Alert ==========
Rule : BG-FILE-001
Name : Sensitive File Modification
Severity : HIGH
Action : MODIFIED
Path : /tmp/behaviorguard_test/etc/passwd
Description : Sensitive system file /etc/passwd was modified.
==============================================
但是接下來短時間內又收到相同事件時:
[SUPPRESSED] BG-FILE-001 duplicate alert for /tmp/behaviorguard_test/etc/passwd (count: 1)
[SUPPRESSED] BG-FILE-001 duplicate alert for /tmp/behaviorguard_test/etc/passwd (count: 2)
[SUPPRESSED] BG-FILE-001 duplicate alert for /tmp/behaviorguard_test/etc/passwd (count: 3)
[SUPPRESSED] BG-FILE-001 duplicate alert for /tmp/behaviorguard_test/etc/passwd (count: 4)
這代表 Alert Manager 成功運作。
整個過程變成:
第一次 BG-FILE-001
↓
ALERT
第二次 BG-FILE-001
↓
SUPPRESSED
count = 1
第三次 BG-FILE-001
↓
SUPPRESSED
count = 2
第四次 BG-FILE-001
↓
SUPPRESSED
count = 3
而且 BehaviorGuard 並沒有停止監控。
File Event 還是持續被收集,只是重複的 Alert 被抑制。
一開始可能會想到:
time.sleep(5)
發完 Alert 之後休息五秒,不就不會一直 Alert 了?
但這其實有問題。
因為如果整個 Monitor 停止五秒:
發現 BG-FILE-001
↓
sleep 5 秒
↓
其他事件?
這段時間可能還有其他重要事件需要處理。
例如:
/etc/passwd MODIFIED
↓
BG-FILE-001 HIGH
1 秒後
/etc/passwd DELETED
↓
BG-FILE-002 CRITICAL
如果因為第一個 Alert 就讓整個 Monitor 暫停,反而可能影響後續事件處理。
所以今天採用的方式是:
Monitor 持續運作
↓
Telemetry 持續收集
↓
Detection Rule 持續分析
↓
Alert Manager 判斷
↓
只 Suppress 重複 Alert
也就是:
Suppress Alert,而不是停止 Detection。
今天最大的收穫是,我開始發現 Detection 不只是「寫規則」。
前幾天我的想法比較像:
發現可疑行為
↓
Rule Match
↓
Alert
但是當監控的 Process、Network、File Event 越來越多之後,就會開始遇到另一個問題:
Alert Noise。
如果一個行為產生十幾個重複 Alert,分析人員看到的可能不是更多資訊,而只是更多雜訊。
因此比較完整的流程應該是:
Telemetry
↓
Detection Engine
↓
Rule Match
↓
Alert Manager
↓
Deduplication
↓
Suppression
↓
Alert
而且 Suppression 也不能把資訊完全丟掉。
所以今天又加入:
suppressed_count
即使 5 秒內壓掉四個重複 Alert,我們還是知道:
Suppressed Count = 4
這樣才能同時做到:
降低 Alert Noise + 保留事件發生頻率。
今天沒有新增更多 Detection Rule,而是開始改善 BehaviorGuard 的 Alert Pipeline。
目前架構:
Process Monitor ────┐
Command Monitor ────┤
Network Monitor ────┼──→ Detection Engine
File Monitor ───────┘
↓
Rule Match
↓
Alert Manager
↓
Deduplication / Suppression
↓
Alert
目前 Alert Manager 先接在 File Monitor 上進行驗證,後續還可以再逐步讓其他 Telemetry 共用這套機制。
今天完成:
做到這裡,我也開始理解一件事情:
好的偵測系統不只是「能不能抓到」,還要考慮「抓到之後產生的資訊有沒有辦法被使用」。
如果每一個底層事件都變成一個 Alert,最後反而可能讓真正重要的警報被淹沒。
下一步再繼續擴充 BehaviorGuard 的 Detection 能力。